#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <ucontext.h>
#include <assert.h>
#include "thread.h"
#include "list.h"

/*  variables globales:
 *  - liste des threads
 *  - pointeur vers le thread courant
 *  - un generateur de pid
 */
struct queue_thread *runq;
struct queue_thread *waitq; // waitq ne se sert pas pour l'instant
thread_t current;
static int ID_INC;
ucontext_t exit_context;

/*
 * libere les ressources d'un thread
 */
void thread_free(thread_t thread){
	if (thread != NULL){
		if (thread->uc.uc_stack.ss_sp != NULL)
			free(thread->uc.uc_stack.ss_sp);
		free(thread);
	}
}

/* initialise la runq et la waitq
 */
void init(void) {
    	runq = new_queue_thread ();
   	waitq = new_queue_thread ();
   	ID_INC = 0;
    
	// initialiser le thread main
	current = malloc(sizeof(*current));
	current->next = NULL;
	current->joiner = NULL;
	current->st = RUNNING;
	current->priority = 20;
	current->uc.uc_stack.ss_size = 64*1024;
  	current->uc.uc_stack.ss_sp = malloc(current->uc.uc_stack.ss_size);
  	current->uc.uc_link = NULL;
	current->retval = NULL;
	getcontext(&(current->uc));

	// generer le context d'exit
    	getcontext(&exit_context);
    	exit_context.uc_stack.ss_size = 64*1024;
  	exit_context.uc_stack.ss_sp = malloc(exit_context.uc_stack.ss_size);
  	exit_context.uc_link = NULL;
  	makecontext(&exit_context, (void (*)(void))thread_exit, 1, (void*)"no return");
}


/*  recupere l'adresse de la structure du thread courant
 */
thread_t thread_self(void)
{
	return current;
}

/* creer un nouveau thread qui va exécuter la fonction func avec l'argument funcarg.
 * renvoie 0 en cas de succès, -1 en cas d'erreur.
 */
int thread_create(thread_t *newthread, void *(*func)(void *), void *funcarg)
{

	thread_t nt = malloc(sizeof(struct thread));	

	nt->id = ID_INC++;

	if(getcontext(&(nt->uc)) == -1)
		return -1;
	
	nt->uc.uc_stack.ss_size = 64*1024;
  	nt->uc.uc_stack.ss_sp = malloc(nt->uc.uc_stack.ss_size);
	if (nt->uc.uc_stack.ss_sp == NULL)
	  return -1;
  	nt->uc.uc_link = &exit_context;
		
	nt->joiner = NULL;
	nt->st = READY;
	nt->priority = 20;
	nt->retval = NULL;
	
	makecontext(&(nt->uc), (void (*)(void)) func, 1, funcarg);
	
	add_thread(runq, nt);
	
	*newthread = nt;

	return 0;
}

/* passer la main à un autre thread, et mettre le thread courrant dans runq.
 */
int thread_yield(void){
    	thread_t old = current;
	if (get_first_thread(runq) != NULL){
		old->st = READY;
		current = extract_first_thread(runq);
		current->st = RUNNING;
		add_thread(runq, old);
		swapcontext(&(old->uc), &(current->uc));
	}

	return 0;		
}

/* attendre la fin d'exécution d'un thread.
 * la valeur renvoyée par le thread est placée dans *retval.
 * si retval est NULL, la valeur de retour est ignorée.
 * @csong
 */
int thread_join(thread_t thread, void **retval){
 	// sem_wait
      	if (thread == NULL || thread->joiner != NULL || runq == NULL ) 
 		return -1;
	if (thread->st != ZOMBIE){
	  	thread->joiner = current;
  		current->st = BLOCKED;
		current = extract_first_thread(runq);
		current->st = RUNNING;
		swapcontext(&(thread->joiner->uc),&(current->uc));
	}
	
	// remplir la variable passée par paramètre si elle n'est pas null
	if (retval != NULL)
		(*retval) = thread->retval;

	thread_free(thread);	
	return 0;
	// sem_post
}

/* terminer le thread courant en renvoyant la valeur de retour retval.
 * cette fonction ne retourne jamais.
 * @csong
 */
void thread_exit(void *retval){
	// sem_wait
	thread_t old = current;
	old->st = ZOMBIE;
	old->retval = retval;
	// reveiller joiner
	thread_t joiner = old->joiner;
	if (joiner != NULL){
		joiner->st = READY;
		add_thread(runq, joiner);
	}
	// swap context courant avec le premier thread dans runq
	if (runq != NULL){
		current = extract_first_thread(runq);
		current->st = RUNNING;
		swapcontext(&(old->uc),&(current->uc));
	}


	// sem_post
}

